package org.jlamp.server;

import org.jlamp.core.runtime.JlampData;
import org.jlamp.core.util.JlampUtils;

import javax.net.ServerSocketFactory;
import java.io.PrintStream;
import java.lang.instrument.Instrumentation;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author zhangcs
 */
public class JlampBootstrap {

    /**
     * 日志输出流
     */
    public final PrintStream log;

    private static JlampBootstrap bootstrap;

    private final Instrumentation inst;

    private JlampServer jlampServer;

    private final AtomicBoolean isBindRef = new AtomicBoolean(false);

    private JlampBootstrap(Instrumentation inst, String args) {
        this.inst = inst;
        log = JlampUtils.logger();

        if (!isBindRef.compareAndSet(false, true)) {
            throw new IllegalStateException("already bind");
        }

        // 获取端口
        int port = getPort();

        if (jlampServer == null) {
            jlampServer = new JlampServer();
        }

        try {
            // 启动服务
            jlampServer.start(port);

            // 记录端口信息
            JlampData data = JlampData.get();
            data.start(String.valueOf(port));

            // 标识已初始化
            JlampStatus.load();
        } catch (Exception e) {
            e.printStackTrace(log);

            isBindRef.set(false);
            throw new IllegalStateException("start server failed");
        }

        Runtime.getRuntime()
                .addShutdownHook(
                        new Thread("jlamp-shutdown-hooker") {
                            @Override
                            public void run() {
                                JlampBootstrap.this.destroy();
                            }
                        }
                );
    }

    /**
     * 获取实例对象
     *
     * @param inst instrumentation对象
     * @param args 参数信息
     * @return 返回实例对象
     */
    @SuppressWarnings("unused")
    public synchronized static JlampBootstrap getInstance(Instrumentation inst, String args) {
        if (bootstrap == null) {
            bootstrap = new JlampBootstrap(inst, args);
        }
        return bootstrap;
    }

    /**
     * 服务是否已启动
     *
     * @return 已启动时返回true
     */
    @SuppressWarnings("unused")
    public boolean isBind() {
        return isBindRef.get();
    }

    /**
     * 销毁，关闭服务
     */
    public void destroy() {
        jlampServer.stop();

        bootstrap = null;
        jlampServer = null;
        isBindRef.set(false);
    }

    /**
     * 端口号下限
     */
    private static final int MIN_PORT = 1024;

    /**
     * 端口号上限
     */
    private static final int MAX_PORT = 65535;

    /**
     * 获取端口号
     *
     * @return 返回可用的随机端口号
     */
    private int getPort() {
        int portRange = MAX_PORT - MIN_PORT;
        int port;
        int searchCounter = 0;
        do {
            if (searchCounter > portRange) {
                String msg = String.format("Could not find an available tcp port in the range [%d, %d] after %d attempts", MIN_PORT, MAX_PORT, searchCounter);
                throw new IllegalStateException(msg);
            }
            port = findRandomPort();
            searchCounter++;
        } while (!isTcpPortAvailable(port));
        return port;
    }

    /**
     * 生成随机端口号
     *
     * @return 返回随机端口号
     */
    private static int findRandomPort() {
        int portRange = MAX_PORT - MIN_PORT;
        Random random = new Random(System.currentTimeMillis());
        return MIN_PORT + random.nextInt(portRange + 1);
    }

    /**
     * 端口是否可用
     *
     * @param port 端口
     * @return 返回端口是否可用，可用时返回true
     */
    public static boolean isTcpPortAvailable(int port) {
        try {
            // 尝试绑定端口建立socket连接，端口已占用时抛出异常
            ServerSocket serverSocket = ServerSocketFactory.getDefault()
                    .createServerSocket(port, 1, InetAddress.getByName("localhost"));
            serverSocket.close();
            return true;
        } catch (Exception ex) {
            return false;
        }
    }


}
